#ifndef __TIMERQUEUE_H__
#define __TIMERQUEUE_H__

#include "Spark/Net/Timer.h"
#include "Spark/Utils/TimePoint.h"
#include "Spark/Utils/NonCopyable.h"
#include <vector>
#include <queue>
#include <set>

class EventLoop;
class Channel;
using TimerPtr = std::shared_ptr<Timer>;
using TimerPtrList = std::vector<TimerPtr>;

/**
 * @brief Compare callable for std::priority_queue<TimerPtr>
 */
struct TimerPtrGreater
{
    bool operator()(const TimerPtr& a, const TimerPtr& b) const
    {
        return *a > *b;
    }
};

using TimerHeap = std::priority_queue<TimerPtr, TimerPtrList, TimerPtrGreater>;

class TimerQueue : public NonCopyable
{
  public:
    explicit TimerQueue(EventLoop* loop);
    ~TimerQueue();

    /**
     * @brief Adds a timer to timerQueue
     * @param  callback callable for timer to run
     * @param  when expected time to run
     * @param  interval if not zero the timer will take effect repeatedly
     * @return TimerID
     */
    template <class P, class R>
    TimerID addTimer(Task callback,
                     TimePoint when,
                     TimeInterval<P, R> interval);

    /**
     * @brief Add a Task to EventLoop to erase given timer
     * @param  id the timer to erase
     */
    void disableTimer(TimerID id);

    /**
     * @brief Restore timerQueue and reset all timers;
     */
    void reset();

  private:
    /**
     * @brief When timerfd received readable event
     * handle timer and run timer callable
     */
    void handleRead();

    /**
     * @brief Add the timer in EventLoop
     * @param  timer
     */
    void addTimerInLoop(TimerPtr timer);

    /**
     * @brief Determine whether  this timer is the ealiers to invoke
     * then add this timer to timerheap.
     * @param  timer timer to push
     * @return true - The timer is earliest, and need to reset timerfd
     * @return false - The timer is not earliest, so no need to reset timerfd
     */
    bool pushTimerHeap(TimerPtr timer);

    /**
     * @brief Get the Expired timer object
     * @param  when
     * @return TimerPtrList
     */
    TimerPtrList getExpired(TimePoint when);

    /**
     * @brief Everytime after calling handleRead()
     * we will need to recycle expired timer
     * @param  expired Expired Timer list
     * @param  when Now
     */
    void recycleTimer(const TimerPtrList& expired, TimePoint when);

  private:
    /**
     * @brief Owner EventLoop Pointer
     */
    EventLoop* loop_;
    /**
     * @brief Linux sys/timerfd.h
     */
    int timerFD_;
    /**
     * @brief The Channel object that manages the timerFD_
     */
    std::shared_ptr<Channel> timerChannel_;
    /**
     * @brief A min heap for TimerPtrs
     */
    TimerHeap timers_;
    /**
     * @brief A set to collect all the timerIDs in timerQueue
     */
    std::set<TimerID> timerIDSet_;
};

#endif  // __TIMERQUEUE_H__